Frontend column sorting#1499
Conversation
…chenko/rocketadmin into order-of-tables-(dnd)
- Add sortable header with dropdown menu for ascending/descending sort - Show sort icon on column header hover, change to active icon when sorted - Add "Set as default" option to persist sort preference across page reloads - Default sort takes priority over URL params on page reload - Clicking selected sort option toggles it off (reverts to default if set) - Style active sort option with gray background highlight Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Style active sort option with rounded highlight background - Auto-remove default sort when user deselects sorting on default column - Disable "Set as default" button until sort order is selected - Add tooltip hint when default button is disabled Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
|
…archenko/rocketadmin into frontend_column_sorting
There was a problem hiding this comment.
Pull request overview
This pull request implements frontend column sorting functionality with the ability to set default sort preferences and reorder columns via drag-and-drop. The changes introduce a new PersonalTableViewSettings interface to manage user-specific table preferences separate from global table settings.
Changes:
- Added sortable column headers with dropdown menus for ascending/descending sort and default sort preferences
- Implemented drag-and-drop column reordering in the columns visibility menu
- Refactored table settings to separate personal view preferences (ordering, columns view) from global table settings
- Added new API endpoint for updating personal table view settings
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 25 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/environments/environment.dev.ts | Updated development API URLs to Tailscale network addresses |
| frontend/src/app/models/table.ts | Created PersonalTableViewSettings interface and removed ordering/list fields from TableSettings |
| frontend/src/app/models/ui-settings.ts | Added defaultSort property to TableSettingsUI interface |
| frontend/src/app/services/ui-settings.service.ts | Modified updateTableSetting to remove table initialization code |
| frontend/src/app/services/tables.service.ts | Added updatePersonalTableViewSettings method and reformatted error handling |
| frontend/src/app/components/dashboard/db-tables-data-source.ts | Updated to use table_settings nested object and added defaultSort support |
| frontend/src/app/components/dashboard/db-table-view/db-table-view.component.ts | Implemented sort initialization logic, drag-drop column reordering, and default sort toggle |
| frontend/src/app/components/dashboard/db-table-view/db-table-view.component.html | Added sortable header UI with dropdown menus and drag-drop column list |
| frontend/src/app/components/dashboard/db-table-view/db-table-view.component.css | Added styles for sortable headers and draggable column menu items |
| frontend/src/app/components/dashboard/db-table-view/db-table-settings/* | Removed deprecated column ordering UI and related logic |
| frontend/src/app/components/dashboard/dashboard.component.ts | Removed UiSettingsService dependency from TablesDataSource initialization |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| this.syncUiSettings().subscribe(); | ||
| } | ||
| updateTableSetting(connectionId: string, tableName: string, key: string, value: any) { | ||
| console.log('updateTableSetting') |
There was a problem hiding this comment.
Missing semicolon at the end of the statement. This is inconsistent with the coding style used throughout the file and could potentially cause issues in certain build configurations.
| console.log('updateTableSetting') | |
| console.log('updateTableSetting'); |
| }), | ||
| catchError((err) => { | ||
| console.log(err); | ||
| this._notifications.showErrorSnackbar(err.error.message); |
There was a problem hiding this comment.
Inconsistent error handling - accessing err.error.message directly without optional chaining. If err.error is undefined, this will throw a runtime error. The original code used err.error?.message || err.message for safer error handling.
| let orderedColumns: TableField[]; | ||
| if (res.table_settings.list_fields.length) { | ||
| orderedColumns = res.structure.sort((fieldA: TableField, fieldB: TableField) => res.table_settings.list_fields.indexOf(fieldA.column_name) - res.table_settings.list_fields.indexOf(fieldB.column_name)); | ||
| } else { | ||
| orderedColumns = [...res.structure]; | ||
| }; | ||
|
|
||
| if (isTablePageSwitched === undefined) this.columns = orderedColumns | ||
| .filter (item => item.isExcluded === false) | ||
| .map((item, index) => { | ||
| if (res.table_settings.columns_view && res.table_settings.columns_view.length !== 0) { | ||
| return { | ||
| title: item.column_name, | ||
| normalizedTitle: this.widgets[item.column_name]?.name || normalizeFieldName(item.column_name), | ||
| selected: res.table_settings.columns_view.includes(item.column_name) |
There was a problem hiding this comment.
The code references res.table_settings.list_fields and res.table_settings.columns_view, but these fields have been moved from TableSettings to PersonalTableViewSettings according to the model changes. This will cause runtime errors if the backend response structure doesn't match these expectations.
| let orderedColumns: TableField[]; | |
| if (res.table_settings.list_fields.length) { | |
| orderedColumns = res.structure.sort((fieldA: TableField, fieldB: TableField) => res.table_settings.list_fields.indexOf(fieldA.column_name) - res.table_settings.list_fields.indexOf(fieldB.column_name)); | |
| } else { | |
| orderedColumns = [...res.structure]; | |
| }; | |
| if (isTablePageSwitched === undefined) this.columns = orderedColumns | |
| .filter (item => item.isExcluded === false) | |
| .map((item, index) => { | |
| if (res.table_settings.columns_view && res.table_settings.columns_view.length !== 0) { | |
| return { | |
| title: item.column_name, | |
| normalizedTitle: this.widgets[item.column_name]?.name || normalizeFieldName(item.column_name), | |
| selected: res.table_settings.columns_view.includes(item.column_name) | |
| const personalViewSettings = (res as any).personal_table_view_settings || {}; | |
| const tableSettings = res.table_settings || {}; | |
| const listFields: string[] = personalViewSettings.list_fields ?? tableSettings.list_fields ?? []; | |
| const columnsView: string[] = personalViewSettings.columns_view ?? tableSettings.columns_view ?? []; | |
| let orderedColumns: TableField[]; | |
| if (listFields.length) { | |
| orderedColumns = res.structure.sort( | |
| (fieldA: TableField, fieldB: TableField) => | |
| listFields.indexOf(fieldA.column_name) - listFields.indexOf(fieldB.column_name) | |
| ); | |
| } else { | |
| orderedColumns = [...res.structure]; | |
| } | |
| if (isTablePageSwitched === undefined) this.columns = orderedColumns | |
| .filter (item => item.isExcluded === false) | |
| .map((item, index) => { | |
| if (columnsView && columnsView.length !== 0) { | |
| return { | |
| title: item.column_name, | |
| normalizedTitle: this.widgets[item.column_name]?.name || normalizeFieldName(item.column_name), | |
| selected: columnsView.includes(item.column_name) |
|
|
||
| this.dataColumns = this.columns.map(column => column.title); | ||
| this.dataNormalizedColumns = this.columns | ||
| .reduce((normalizedColumns, column) => (normalizedColumns[column.title] = column.normalizedTitle, normalizedColumns), {}) |
There was a problem hiding this comment.
Missing semicolons at the end of multiple statements. This is inconsistent with TypeScript coding standards.
| .reduce((normalizedColumns, column) => (normalizedColumns[column.title] = column.normalizedTitle, normalizedColumns), {}) | |
| .reduce((normalizedColumns, column) => (normalizedColumns[column.title] = column.normalizedTitle, normalizedColumns), {}); |
| return totalActionsCount === 0 ? '0' : `${lengthValue}px` | ||
| } | ||
|
|
||
| changleColumnList(connectionId: string, tableName: string) { | ||
| this.displayedDataColumns = (filter(this.columns, column => column.selected === true)).map(column => column.title); | ||
| if (this.keyAttributes.length) { | ||
| this.displayedColumns = ['select', ...this.displayedDataColumns, 'actions' ] | ||
| } else { | ||
| this.displayedColumns = [...this.displayedDataColumns]; | ||
| }; | ||
| } | ||
|
|
||
| getQueryParams(row, action) { | ||
| const params = Object.fromEntries(this.keyAttributes.map((column) => { | ||
| if (this.foreignKeysList.includes(column.column_name)) { | ||
| const referencedColumnNameOfForeignKey = this.foreignKeys[column.column_name].referenced_column_name; | ||
| return [column.column_name, row[column.column_name][referencedColumnNameOfForeignKey]]; | ||
| }; |
There was a problem hiding this comment.
Missing semicolons at the end of statements on lines 319, 328, and 336. This is inconsistent with TypeScript coding standards.
| console.log('loading$ changed:', loading, 'sort.active:', this.sort?.active, 'sort.direction:', this.sort?.direction); | ||
|
|
||
| if (!loading && this.tableData.defaultSort !== undefined) { | ||
| // Update defaultSort reference whenever data loads | ||
| this.defaultSort = this.tableData.defaultSort; | ||
|
|
||
| console.log('DbTableViewComponent tableData loaded:', this.tableData); | ||
| console.log('DbTableViewComponent tableData.defaultSort loaded:', this.tableData.defaultSort); | ||
|
|
||
| // Only initialize sort on first load (or after table switch when sortInitialized was reset) | ||
| if (!this.sortInitialized) { | ||
| this.sortInitialized = true; | ||
|
|
||
| // Initialize sort based on priority: URL params > default sort | ||
| if (urlSortActive && urlSortDirection) { | ||
| // Use sort from URL | ||
| this.sort.active = urlSortActive; | ||
| this.sort.direction = urlSortDirection.toLowerCase() as 'asc' | 'desc'; | ||
| } else if (this.defaultSort) { | ||
| // Use default sort if no URL params | ||
| this.sort.active = this.defaultSort.column; | ||
| this.sort.direction = this.defaultSort.direction; | ||
| } | ||
| } | ||
|
|
||
| console.log('After loading complete - sort.active:', this.sort?.active, 'sort.direction:', this.sort?.direction); | ||
| } |
There was a problem hiding this comment.
Console.log statements left in production code. These debug logs should be removed before merging to production as they can impact performance and expose internal implementation details.
| } | ||
|
|
||
| onColumnVisibilityChange() { | ||
| this.tableData.changleColumnList(this.connectionID, this.name); |
There was a problem hiding this comment.
Method name changleColumnList contains a typo - it should be changeColumnList. This is a pre-existing typo but calling it from new code perpetuates the naming issue.
| // if (!this.settings.connections[connectionId].tables[tableName]) { | ||
| // this.settings.connections[connectionId].tables[tableName] = { shownColumns: [] }; | ||
| // } | ||
| this.settings.connections[connectionId].tables[tableName][key] = value; |
There was a problem hiding this comment.
Commented out code that initializes the table object will cause a runtime error. The code tries to access this.settings.connections[connectionId].tables[tableName] on line 57, but this object may not exist since the initialization code is commented out. This will throw an error when trying to set a property on undefined.
| // if (!this.settings.connections[connectionId].tables[tableName]) { | |
| // this.settings.connections[connectionId].tables[tableName] = { shownColumns: [] }; | |
| // } | |
| this.settings.connections[connectionId].tables[tableName][key] = value; | |
| const connectionSettings = this.settings.connections[connectionId]; | |
| if (!connectionSettings.tables) { | |
| connectionSettings.tables = {}; | |
| } | |
| if (!connectionSettings.tables[tableName]) { | |
| connectionSettings.tables[tableName] = { shownColumns: [] }; | |
| } | |
| connectionSettings.tables[tableName][key] = value; |
|
|
||
| this.dataColumns = this.columns.map(column => column.title); | ||
| this.dataNormalizedColumns = this.columns | ||
| .reduce((normalizedColumns, column) => (normalizedColumns[column.title] = column.normalizedTitle, normalizedColumns), {}) |
There was a problem hiding this comment.
Avoid automated semicolon insertion (95% of all statements in the enclosing function have an explicit semicolon).
| .reduce((normalizedColumns, column) => (normalizedColumns[column.title] = column.normalizedTitle, normalizedColumns), {}) | |
| .reduce((normalizedColumns, column) => (normalizedColumns[column.title] = column.normalizedTitle, normalizedColumns), {}); |
| to: 'https://docs.rocketadmin.com/' | ||
| } | ||
| ] | ||
| } |
There was a problem hiding this comment.
Avoid automated semicolon insertion (95% of all statements in the enclosing function have an explicit semicolon).
No description provided.